﻿// NaverQueryProvider.cs
//

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Reflection;

namespace LinqProviders.LinqToNaver {

    internal sealed class NaverQueryProvider : IQueryProvider {

        private string _appKey;

        public NaverQueryProvider(string appKey) {
            _appKey = appKey;
        }

        public IQueryable<TElement> CreateQuery<TElement>(Expression expression) {
            return new NaverQuery<TElement>(this, expression);
        }

        public IQueryable CreateQuery(Expression expression) {
            Type elementType = GetElementType(expression.Type);
            try {
                return (IQueryable)Activator.CreateInstance(typeof(NaverQuery<>).MakeGenericType(elementType), new object[] { this, expression });
            }
            catch (TargetInvocationException tie) {
                throw tie.InnerException;
            }
        }

        public TResult Execute<TResult>(Expression expression)
        {
            int dummyTotalCount;
            return (TResult)Execute(expression, out dummyTotalCount);
        }

        public TResult Execute<TResult>(Expression expression, out int totalCount) {
            return (TResult)Execute(expression, out totalCount);
        }

        public object Execute(Expression expression)
        {
            int dummyTotalCount;
            return Execute(expression, out dummyTotalCount);
        }

        public object Execute(Expression expression, out int totalCount) {
            Type elementType = GetElementType(expression.Type);
            string query;

            totalCount = 0;

            NaverUriBuilder uriBuilder = new NaverUriBuilder(elementType, _appKey);
            Uri uri = uriBuilder.BuildUri(expression, out query);


            NaverSearchResult searcher = NaverQuerytypeFactory.Create(elementType);
            return searcher.Execut(uri, query);

        }

        private static Type FindIEnumerable(Type sequenceType) {
            if ((sequenceType == null) || (sequenceType == typeof(string))) {
                return null;
            }

            if (sequenceType.IsArray) {
                return typeof(IEnumerable<>).MakeGenericType(sequenceType.GetElementType());
            }

            if (sequenceType.IsGenericType) {
                foreach (Type arg in sequenceType.GetGenericArguments()) {
                    Type ienum = typeof(IEnumerable<>).MakeGenericType(arg);
                    if (ienum.IsAssignableFrom(sequenceType)) {
                        return ienum;
                    }
                }
            }

            Type[] interfaceTypes = sequenceType.GetInterfaces();
            if ((interfaceTypes != null) && (interfaceTypes.Length != 0)) {
                foreach (Type interfaceType in interfaceTypes) {
                    Type enumerableType = FindIEnumerable(interfaceType);
                    if (enumerableType != null) {
                        return enumerableType;
                    }
                }
            }

            if ((sequenceType.BaseType != null) && (sequenceType.BaseType != typeof(object))) {
                return FindIEnumerable(sequenceType.BaseType);
            }
            return null;
        }

        private static Type GetElementType(Type sequenceType) {
            Type enumerableType = FindIEnumerable(sequenceType);
            if (enumerableType == null) {
                return sequenceType;
            }
            return enumerableType.GetGenericArguments()[0];
        }
    }
}
